require "rubygems"
require "highline" 
require "capistrano"

module Wondercap
  
  class CLIError < RuntimeError
  end
  
  module CLIParser
    attr_reader :command, :arg, :vars
    
    def parse_args(argv, env)
      @command = argv[0]
      @arg = argv[1] if argv.length >= 2
      (argv[2..-1] || []).each do |ar|
        if ar =~ /^(\w+)=(.+)$/
          @vars[$1.to_sym] = $2
        else
          @vars[ar.to_sym] = true
        end
      end
    end
  end
  
  module CLICommandExecutor
    def set_names(default = nil)
      raise CLIError.new("names should be specified") if @cli_parser.arg.nil? && !default
      @configuration.set :names, @cli_parser.arg || default
    end
    private :set_names
    
    def set_vars(except = [])
      @cli_parser.vars.each do |name, var|
        raise CLIError.new("can't set variable #{name}") if except.include? name
        @configuration.set name, var
      end
    end
    private :set_vars
    
    def init_config(&block)
      @configuration_initializer.call do |conf|
        @configuration = conf
        block.call conf
      end
    end
    
    def prepare_exec_task(task_name = @configuration.fetch(:task))
        @configuration.set :task, task_name
        filters = @configuration.fetch(:filters, "").split(/,/)
        filters << "task"
        @configuration.set :filters, filters.join(",")
    end
      
    module Commands
      def deploy
        init_config do
          set_names
          set_vars
        end
        if @configuration.fetch(:remote, false)
          @configuration.find_and_execute_task("wondercap:remote_deployment")
        else
          raise "can't specify area for local deployment" if @configuration.fetch(:area, nil)
          @configuration.find_and_execute_task("wondercap:local_build_deployment")
        end
      end
            
      def exec
        init_config do
          set_names
          set_vars
          prepare_exec_task
        end
        @configuration.find_and_execute_task("wondercap:execute_task_on_names")
      end      
      
      def inspect
        init_config do
          set_vars
        end
        inspect_type = :area
        unless @cli_parser.arg.nil?
          if @cli_parser.arg =~ /^(\w+):(\w+)$/
            @configuration.set :area, $1
            @configuration.set :sid, $2 unless $2.casecmp("CUR") == 0
            inspect_type = :snapshot
          else
            if @cli_parser.arg =~ /^(\d+)$/
              @configuration.set :sid, @cli_parser.arg
              inspect_type = :snapshot
            elsif @cli_parser.arg.casecmp("CUR") == 0
              inspect_type = :snapshot
            else
              @configuration.set :area, @cli_parser.arg
            end
          end
        end
        @configuration.find_and_execute_task("wondercap:inspect_#{inspect_type.to_s}")
      end

      def list
        init_config do
          set_names("all")
          set_vars
        end
        @configuration.find_and_execute_task("wondercap:list_names")
      end
      
      def restart
        init_config do
          set_names
          set_vars
          prepare_exec_task("restart")
        end
        @configuration.find_and_execute_task("wondercap:execute_task_on_names")
      end
      
      def selenium_test
        init_config do
          set_names
          set_vars
          prepare_exec_task("selenium_test")
        end
        @configuration.find_and_execute_task("wondercap:execute_task_on_names")
      end
    end
    include Commands
    
    module CommandsHelp
      def deploy_help
        @ui.say "deploy all|name1[,name2] [remote] [force] [with_deps] [verbose=1..4] [filters=filter1,[filter2]]"
        @ui.say "  deploy project (locally by default)"
      end
      
      def exec_help
        @ui.say "exec all|name1[,name2] task=task_name [filters=filter1,[filter2]]"
        @ui.say "  execute specified task on specified names"
      end
      
      def inspect_help
        @ui.say "inspect area_name|snapshot_id|area_name:snapshot_id"
        @ui.say "  inspect current snapshot or area (can use CUR as snapshot_id, which means current snapshot)"
      end
      
      def list_help
        @ui.say "list [all] [filters=filter1[,filter2]]"
        @ui.say "  list available configurations"
      end
      
      def restart_help
        @ui.say "restart all|name1,[name2] [filters=filter1[,filter2]]"
        @ui.say "  restart given applications"        
      end

      def selenium_test_help
        @ui.say "selenium_test all|name1,[name2] [filters=filter1[,filter2]]"
        @ui.say "  run selenium tests on given applications"        
      end
    end
    include CommandsHelp
    
    def execute
      if @cli_parser.command.nil?
        help
        return
      end
      
      if Commands.instance_methods.include? @cli_parser.command
        send @cli_parser.command.to_sym
      else
        raise CLIError.new("unknown command: #{@cli_parser.command}")
      end
    end
    
    def help
      @ui.say "wcap command ..."
      @ui.say "\n"
      CommandsHelp.instance_methods.each do |name|
        send name
      end
    end
  end
  
  class CLI
    def default_configuration_initializer(&block)
      result = Capistrano::Configuration.new
      yield result if block_given?
      result.load_paths << File.join(File.dirname(__FILE__), "..", "..", "..")
      result.load("wondercap/lib/deployment/recipes/deployment")
      result.trigger :load
      result
    end
    
    def default_command_executor(cli_parser)
      CLICommandExecutor.new(@ui, cli_parser, method(:default_configuration_initializer)).execute
    end
    
    def initialize(ui = HighLine.new, cli_parser = CLIParser.new, executor = method(:default_command_executor))
      @ui = ui
      @cli_parser = cli_parser
      @executor = executor
    end
    
    def execute(args, env)
      trap "INT" do
        @ui.say(caller.join("\n\t"))
        exit 1
      end
      begin
        @cli_parser.parse(args, env)

        @executor.call(@cli_parser)
      rescue CLIError => e
        @ui.say "error: #{e}"
      rescue => e
        @ui.say "error: #{e}"
        @ui.say(e.backtrace.join("\n\t"))
        raise e
      end
    end
  end
  
end